<template>
  <div :style="{width: data.config && data.config.width}" class="fm-form" :class="'fm-'+formStyleKey">
    <el-form :ref="formRef"
      :key="formKey"
      v-if="formShow"
      :class="{
        [data.config && data.config.customClass]:  (data.config && data.config.customClass) ? true : false,
        'no-label-form': data.config && (data.config.labelWidth == 0)
      }"
      :size="data.config && data.config.size"
      :model="models"
      :rules="rules"
      :label-position="data.config && data.config.labelPosition"
      :disabled="!edit"
      :label-width="data.config && data.config.labelWidth + 'px'">

      <template v-for="item in data.list">
        <generate-col-item
          v-if="item.type == 'grid'"
          :key="item.key"
          :model="models"
          :rules="rules"
          :element="item"
          :remote="remote"
          :blanks="blanks"
          :display="displayFields"
          @input-change="onInputChange"
          :edit="edit"
          :remote-option="remoteOption"
          :platform="platform"
          :preview="preview"
          :container-key="containerKey"
          :data-source-value="dataSourceValue"
          :event-function="eventFunction"
          :print-read="printRead"
          :form-component="$refs[formRef]"
        >
          <template v-slot:[blank.name]="scope" v-for="blank in blanks">
            <slot :name="blank.name" :model="scope.model"></slot>
          </template>
        </generate-col-item>

        <generate-tab-item
          v-else-if="item.type == 'tabs'"
          :key="item.key"
          :model="models"
          :rules="rules"
          :element="item"
          :remote="remote"
          :blanks="blanks"
          :display="displayFields"
          @input-change="onInputChange"
          :edit="edit"
          :remote-option="remoteOption"
          :platform="platform"
          :preview="preview"
          :container-key="containerKey"
          :data-source-value="dataSourceValue"
          :event-function="eventFunction"
          :print-read="printRead"
          :form-component="$refs[formRef]"
        >
          <template v-slot:[blank.name]="scope" v-for="blank in blanks">
            <slot :name="blank.name" :model="scope.model"></slot>
          </template>
        </generate-tab-item>

        <generate-report
          v-else-if="item.type == 'report'"
          :key="item.key"
          :model="models"
          :rules="rules"
          :element="item"
          :remote="remote"
          :blanks="blanks"
          :display="displayFields"
          @input-change="onInputChange"
          :edit="edit"
          :remote-option="remoteOption"
          :platform="platform"
          :preview="preview"
          :container-key="containerKey"
          :data-source-value="dataSourceValue"
          :event-function="eventFunction"
          :print-read="printRead"
          :form-component="$refs[formRef]"
        >
          <template v-slot:[blank.name]="scope" v-for="blank in blanks">
            <slot :name="blank.name" :model="scope.model"></slot>
          </template>
        </generate-report>

        <generate-inline
          v-else-if="item.type == 'inline'"
          :key="item.key"
          :model="models"
          :rules="rules"
          :element="item"
          :remote="remote"
          :blanks="blanks"
          :display="displayFields"
          @input-change="onInputChange"
          :edit="edit"
          :remote-option="remoteOption"
          :platform="platform"
          :preview="preview"
          :container-key="containerKey"
          :data-source-value="dataSourceValue"
          :event-function="eventFunction"
          :print-read="printRead"
          :form-component="$refs[formRef]"
        >
          <template v-slot:[blank.name]="scope" v-for="blank in blanks">
            <slot :name="blank.name" :model="scope.model"></slot>
          </template>
        </generate-inline>

        <generate-form-item
          v-else
          :key="item.key"
          :models="models"
          :rules="rules"
          :widget="item"
          :remote="remote"
          :blanks="blanks"
          :display="displayFields"
          @input-change="onInputChange"
          :edit="edit"
          :remote-option="remoteOption"
          :platform="platform"
          :preview="preview"
          :container-key="containerKey"
          :data-source-value="dataSourceValue"
          :event-function="eventFunction"
          :print-read="printRead"
          :form-component="$refs[formRef]"
        >
          <template v-slot:[blank.name]="scope" v-for="blank in blanks">
            <slot :name="blank.name" :model="scope.model"></slot>
          </template>
        </generate-form-item>
      </template>
    </el-form>
  </div>
</template>

<script>
import GenerateFormItem from './GenerateFormItem'
import GenerateColItem from './GenerateColItem'
import GenerateTabItem from './GenerateTabItem'
import GenerateReport from './GenerateReport'
import GenerateInline from './GenerateInline'
import {loadJs, updateStyleSheets, splitStyleSheets, clearStyleSheets} from '../util/index.js'
import {EventBus} from '../util/event-bus.js'
import _ from 'lodash'

export default {
  name: 'fm-generate-form',
  components: {
    GenerateFormItem,
    GenerateColItem,
    GenerateTabItem,
    GenerateReport,
    GenerateInline
  },
  props: {
    data: {
      type: Object,
      default: () => ({
        'list': [],
        'config': {
          'labelWidth': 100,
          'labelPosition': 'right',
          'size': 'small',
          'customClass': '',
          'ui': 'element',
          'layout': 'horizontal',
          'labelCol': 3
        }
      })
    },
    remote: {
      type: Object,
      default: () => ({})
    },
    value: {
      type: Object,
      default: () => ({})
    },
    edit: {
      type: Boolean,
      default: true
    },
    printRead: {
      type: Boolean,
      default: false
    },
    remoteOption: {
      type: Object,
      default: () => ({})
    },
    preview: {
      type: Boolean,
      default: false
    },
    platform: {
      type: String,
      default: 'pc'
    }
  },
  data () {
    return {
      models: {},
      rules: {},
      blanks: [],
      displayFields: {},
      dataBindFields: [],
      generateShow: false,
      resetModels: {},
      formKey: Math.random().toString(36).slice(-8),
      formStyleKey: Math.random().toString(36).slice(-8),
      formValue: this.value,
      formShow: false,
      formRef: Math.random().toString(36).slice(-8) + 'Form',
      containerKey: Math.random().toString(36).slice(-8),
      dataSourceValue: [],
      eventFunction: {}
    }
  },
  created () {
    this.loadDataSource()

    this.loadEvents()

    this._initForm()
  },
  mounted () {
    const _this = this

    EventBus.$on('on-change-' + this.containerKey, (value, field, containerKey) => {
      if (this.containerKey == containerKey) {
        _this.setData({
          [field]: value
        })
      }

      _this.$emit('on-change', field, value, _this.models)
      _this.$emit(`on-${field}-change`, value)
    })

    this.$nextTick(() => {
      this.eventFunction.mounted && this.eventFunction['mounted']()
    })
  },
  beforeDestroy () {
    let head = '.fm-' + this.formStyleKey + ' '
    clearStyleSheets(head)
    EventBus.$off('on-change-' + this.containerKey)
  },
  methods: {
    _initForm () {
      this.formShow = false

      if (Object.keys(this.data).length) {
        this.generateModel(this.data.list)
      } else {
        this.generateModel([])
      }

      this.resetModels = _.cloneDeep(this.models)

      this.models = {...this.models}

      this.formShow = true

      if (this.data.config && this.data.config.styleSheets) {
        let head = '.fm-' + this.formStyleKey + ' '

        updateStyleSheets(splitStyleSheets(this.data.config.styleSheets), head)
      }
    },
    loadEvents () {
      if (this.data.config && this.data.config.eventScript) {
        for (let i = 0; i < this.data.config.eventScript.length; i++) {
          let currentScript = this.data.config.eventScript[i]

          this.eventFunction[currentScript.key] = Function(currentScript.func).bind(this)
        }
      }
    },
    loadDataSource () {
      if (this.data.config && this.data.config.dataSource) {
        for (let i = 0; i < this.data.config.dataSource.length; i++) {
          let currentDataSource = this.data.config.dataSource[i]

          if (currentDataSource.auto) {
            this.sendRequest(currentDataSource.name)
          }
        }
      }
    },
    sendRequest (name) {
      let currentDataSource = this.data.config.dataSource.find(item => item.name === name)

      if (currentDataSource) {
        let options = {
          method: currentDataSource.method,
          url: currentDataSource.url,
          headers: currentDataSource.headers,
          params: currentDataSource.params
        }

        // 请求发送前处理函数
        if (currentDataSource.requestFunc) {
          const requestDynamicFunc = Function('config', currentDataSource.requestFunc).bind(this)

          options = requestDynamicFunc(options)
        }

        this.$send(options).then(res => {
          let data = res

          if (currentDataSource.responseFunc) {
            const dynamicFunc = Function('res', currentDataSource.responseFunc).bind(this)

            data = dynamicFunc(res.data)
          }

          let sourceValue = this.dataSourceValue.find(item => item.key === currentDataSource.key)

          if (sourceValue) {
            sourceValue.value = data
          } else {
            this.dataSourceValue.push({
              key: currentDataSource.key,
              value: data
            })
          }
        }).catch((error) => {
          // 请求错误处理函数
          if (currentDataSource.errorFunc) {
            const errorDynamicFunc = Function('error', currentDataSource.errorFunc).bind(this)

            errorDynamicFunc(error)
          }
        })
      }
    },
    generateModel (genList) {
      for (let i = 0; i < genList.length; i++) {
        if (genList[i].type === 'grid') {
          this.displayFields[genList[i].model] = !genList[i].options.hidden

          genList[i].columns.forEach(item => {
            this.generateModel(item.list)
          })
        } else if (genList[i].type === 'tabs') {
          genList[i].tabs.forEach(item => {
            this.generateModel(item.list)
          })

          this.displayFields[genList[i].model] = !genList[i].options.hidden
        } else if (genList[i].type === 'report') {
          genList[i].rows.forEach(row => {
            row.columns.forEach(column => {
              this.generateModel(column.list)
            })
          })

          this.displayFields[genList[i].model] = !genList[i].options.hidden
        } else if (genList[i].type === 'inline') {
          this.generateModel(genList[i].list)

          this.displayFields[genList[i].model] = !genList[i].options.hidden
        } else {
          if (Object.keys(this.formValue).indexOf(genList[i].model) >= 0) {
            // 处理老版本没有dataBind值的情况，默认绑定数据
            if ((Object.keys(genList[i].options).indexOf('dataBind') < 0 || genList[i].options.dataBind) && genList[i].key && genList[i].model) {
              this.models[genList[i].model] = this.formValue[genList[i].model]
              this.dataBindFields.push(genList[i].model)
            }

            this.displayFields[genList[i].model] = !genList[i].options.hidden

            if (genList[i].type === 'blank') {
              this.blanks.push({
                name: genList[i].model
              })
            }
          } else {
            if (genList[i].type === 'blank') {
              // bound the default value
              if ((Object.keys(genList[i].options).indexOf('dataBind') < 0 || genList[i].options.dataBind) && genList[i].key && genList[i].model) {
                this.models[genList[i].model] = genList[i].options.defaultType === 'String' ? '' : (genList[i].options.defaultType === 'Object' ? {} : [])
                this.dataBindFields.push(genList[i].model)
              }
              this.displayFields[genList[i].model] = !genList[i].options.hidden

              this.blanks.push({
                name: genList[i].model
              })
            } else {
              if ((Object.keys(genList[i].options).indexOf('dataBind') < 0 || genList[i].options.dataBind) && genList[i].key && genList[i].model) {
                this.models[genList[i].model] = genList[i].options.defaultValue
                this.dataBindFields.push(genList[i].model)
              }
              this.displayFields[genList[i].model] = !genList[i].options.hidden
            }
          }

          genList[i].tableColumns && genList[i].tableColumns.length && genList[i].tableColumns.forEach(item => {
            if (item.type === 'blank') {
              this.blanks.push({
                name: item.model
              })
            }

            // 处理 rules
            if (this.rules[`${genList[i].model}.${item.model}`]) {
              this.rules[`${genList[i].model}.${item.model}`] = [
                ...this.rules[`${genList[i].model}.${item.model}`],
                ...(item.rules ? item.rules.map(im => {
                  if (im.pattern) {
                    return {...im, pattern: eval(im.pattern)}
                  } else if (im.func) {
                    const validatorFunc = Function('rule', 'value', 'callback', im.func).bind(this)

                    return {...im, validator: validatorFunc}
                  } else {
                    return {...im}
                  }
                }) : [])
              ]
            } else {
              this.rules[`${genList[i].model}.${item.model}`] = [
                ...(item.rules ? item.rules.map(im => {
                  if (im.pattern) {
                    return {...im, pattern: eval(im.pattern)}
                  } else if (im.func) {
                    const validatorFunc = Function('rule', 'value', 'callback', im.func).bind(this)

                    return {...im, validator: validatorFunc}
                  } else {
                    return {...im}
                  }
                }) : [])
              ]
            }
          })

          if (this.rules[genList[i].model]) {
            this.rules[genList[i].model] = [...this.rules[genList[i].model], ...(genList[i].rules ? genList[i].rules.map(item => {
              if (item.pattern) {
                return {...item, pattern: eval(item.pattern)}
              } else if (item.func) {
                const validatorFunc = Function('rule', 'value', 'callback', item.func).bind(this)

                return {...item, validator: validatorFunc}
              } else {
                return {...item}
              }
            }) : [])]
          } else {
            this.rules[genList[i].model] = [...(genList[i].rules ? genList[i].rules.map(item => {
              if (item.pattern) {
                return {...item, pattern: eval(item.pattern)}
              } else if (item.func) {
                const validatorFunc = Function('rule', 'value', 'callback', item.func).bind(this)

                return {...item, validator: validatorFunc}
              } else {
                return {...item}
              }
            }) : [])]
          }
        }
      }
    },
    _setDisabled (genList, fields, disabled) {
      for (let i = 0; i < genList.length; i++) {
        if (genList[i].type === 'grid') {
          genList[i].columns.forEach(item => {
            this._setDisabled(item.list, fields, disabled)
          })
        } else if (genList[i].type === 'tabs') {
          genList[i].tabs.forEach(item => {
            this._setDisabled(item.list, fields, disabled)
          })
        } else if (genList[i].type === 'report') {
          genList[i].rows.forEach(row => {
            row.columns.forEach(column => {
              this._setDisabled(column.list, fields, disabled)
            })
          })
        } else if (genList[i].type === 'inline') {
          this._setDisabled(genList[i].list, fields, disabled)
        } else {
          if (fields.indexOf(genList[i].model) >= 0) {
            this.$set(genList[i].options, 'disabled', disabled)
          }
        }
      }
    },
    _updateClassName (genList, fields, className, updateType) {
      for (let i = 0; i < genList.length; i++) {
        if (genList[i].type === 'grid') {
          genList[i].columns.forEach(item => {
            this._updateClassName(item.list, fields, className, updateType)
          })
        } else if (genList[i].type === 'tabs') {
          genList[i].tabs.forEach(item => {
            this._updateClassName(item.list, fields, className, updateType)
          })
        } else if (genList[i].type === 'report') {
          genList[i].rows.forEach(row => {
            row.columns.forEach(column => {
              this._updateClassName(column.list, fields, className, updateType)
            })
          })
        } else if (genList[i].type === 'inline') {
          this._updateClassName(genList[i].list, fields, className, updateType)
        } else {
          if (fields.indexOf(genList[i].model) >= 0) {
            if (updateType == 'add' && !genList[i].options.customClass.split(' ').includes(className)) {
              this.$set(genList[i].options, 'customClass', [...genList[i].options.customClass.split(' '), className].join(' '))
            }

            if (updateType == 'remove' && genList[i].options.customClass.split(' ').includes(className)) {
              let originArray = genList[i].options.customClass.split(' ')
              originArray.splice(originArray.findIndex(item => item == className), 1)
              this.$set(genList[i].options, 'customClass', originArray.join(' '))
            }
          }
        }
      }
    },
    _setOptions (genList, fields, opts) {
      for (let i = 0; i < genList.length; i++) {
        if (genList[i].type === 'grid') {
          genList[i].columns.forEach(item => {
            this._setOptions(item.list, fields, opts)
          })
        } else if (genList[i].type === 'tabs') {
          genList[i].tabs.forEach(item => {
            this._setOptions(item.list, fields, opts)
          })
        } else if (genList[i].type === 'report') {
          genList[i].rows.forEach(row => {
            row.columns.forEach(column => {
              this._setOptions(column.list, fields, opts)
            })
          })
        } else if (genList[i].type === 'inline') {
          this._setOptions(genList[i].list, fields, opts)
        } else {
          if (fields.indexOf(genList[i].model) >= 0) {
            Object.keys(opts).forEach(key => {
              this.$set(genList[i].options, key, opts[key])
            })
          }
        }
      }
    },
    getData (isValidate = true) {
      return new Promise((resolve, reject) => {
        if (isValidate) {
          this.$refs[this.formRef].validate(valid => {
            if (valid) {
              resolve(JSON.parse(JSON.stringify(this.models)))
            } else {
              reject(new Error(this.$t('fm.message.validError')).message)
            }
          })
        } else {
          resolve(JSON.parse(JSON.stringify(this.models)))
        }
      })
    },
    reset () {
      this.setData(_.cloneDeep(this.resetModels))

      this.$nextTick(() => {
        this.$refs[this.formRef].clearValidate()
      })
    },
    onInputChange (value, field) {
      this.$emit('on-change', field, value, this.models)
      this.$emit(`on-${field}-change`, value)
    },
    display (fields) {
      Object.keys(this.displayFields).forEach(key => {
        if (fields.indexOf(key) >= 0) {
          this.$set(this.displayFields, key, true)
        }
      })

      this.displayFields = {...this.displayFields}
    },
    hide (fields) {
      Object.keys(this.displayFields).forEach(key => {
        if (fields.indexOf(key) >= 0) {
          this.$set(this.displayFields, key, false)
        }
      })

      this.displayFields = {...this.displayFields}
    },
    disabled (fields, disabled) {
      this._setDisabled(this.data.list, fields, disabled)
    },
    addClassName (fields, className) {
      this._updateClassName(this.data.list, fields, className, 'add')
    },
    removeClassName (fields, className) {
      this._updateClassName(this.data.list, fields, className, 'remove')
    },
    refresh () {
      this.loadDataSource()

      this.loadEvents()

      this._initForm()
    },
    setData (value) {
      Object.keys(value).forEach(item => {
        this.$set(this.models, item, value[item])
      })
    },
    _findComponentDownward (context, name, result) {
      let children = context.$children

      if (result) {
        return result
      }

      if (children.length == 0) {
        result = null
      } else {
        for (let i = 0; i < children.length; i++) {
          let currentComponent = children[i]

          let currentRefArray = Object.keys(currentComponent.$refs)

          if (currentRefArray.indexOf(name) >= 0) {
            result = currentComponent.$refs[name]
            break
          } else {
            result = this._findComponentDownward(currentComponent, name, result)
          }
        }
      }
      return result
    },
    getComponent (refName) {
      return this._findComponentDownward(this, refName, null)
    },
    getValues () {
      return this.models
    },
    getValue (fieldName) {
      return this.models[fieldName]
    },
    setRules (field, rules) {
      this.$set(this.rules, field, [...rules])

      this.$refs[this.formRef].clearValidate(field)
    },
    setOptions (fields, options) {
      this._setOptions(this.data.list, fields, options)
    }
  }
}
</script>

<style lang="scss">
</style>
